{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# Routing to IO\n",
    "\n",
    "## Routing electrical\n",
    "For routing low speed DC electrical ports you can use sharp corners instead of smooth bends.\n",
    "\n",
    "You can also define `port.orientation = None` to ignore the port orientation for low speed DC ports."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1",
   "metadata": {},
   "source": [
    "For single route between ports you can use `route_single_electrical`.\n",
    "\n",
    "### route_single_electrical\n",
    "\n",
    "\n",
    "`route_single_electrical` has `bend = wire_corner` with a 90deg bend corner."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "from gdsfactory.samples.big_device import big_device\n",
    "\n",
    "gf.config.rich_output()\n",
    "\n",
    "c = gf.Component()\n",
    "pt = c << gf.components.pad_array(port_orientation=270, columns=3)\n",
    "pb = c << gf.components.pad_array(port_orientation=90, columns=3)\n",
    "pt.move((70, 200))\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "pt = c << gf.components.pad_array(port_orientation=270, columns=3)\n",
    "pb = c << gf.components.pad_array(port_orientation=90, columns=3)\n",
    "pt.move((70, 200))\n",
    "route = gf.routing.route_single_electrical(\n",
    "    c,\n",
    "    pt.ports[\"e11\"],\n",
    "    pb.ports[\"e11\"],\n",
    "    start_straight_length=20,\n",
    "    cross_section=\"metal_routing\",\n",
    ")\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4",
   "metadata": {},
   "source": [
    "There is also `bend = wire_corner45` for 45deg bend corner with parametrizable \"radius\":"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "pt = c << gf.components.pad_array(port_orientation=270, columns=1, centered_ports=False)\n",
    "pb = c << gf.components.pad_array(port_orientation=90, columns=1, centered_ports=False)\n",
    "pt.move((300, 300))\n",
    "route = gf.routing.route_single(\n",
    "    c,\n",
    "    pt.ports[\"e11\"],\n",
    "    pb.ports[\"e11\"],\n",
    "    bend=\"wire_corner45\", # This specifies that the bends should be 45 degrees instead of the standard 90 degree bends.\n",
    "    port_type=\"electrical\", # This informs the router that it is connecting electrical ports, which can influence how the connection is made.\n",
    "\n",
    "    # This tells the router to use a pre-defined cross-section named \"metal_routing,\"\n",
    "    # which specifies the physical properties of the trace, such as its width and GDSII layer.\n",
    "    cross_section=\"metal_routing\",\n",
    "    allow_width_mismatch=True,\n",
    ")\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "pt = c << gf.components.pad_array(port_orientation=270, columns=1, centered_ports=False)\n",
    "pb = c << gf.components.pad_array(port_orientation=90, columns=1, centered_ports=False)\n",
    "pt.move((400, 400))\n",
    "route = gf.routing.route_single(\n",
    "    c,\n",
    "    pt.ports[\"e11\"],\n",
    "    pb.ports[\"e11\"],\n",
    "    bend=\"wire_corner45\", \n",
    "    radius=100,\n",
    "    cross_section=\"metal_routing\",\n",
    "    port_type=\"electrical\",\n",
    "    allow_width_mismatch=True,\n",
    ")\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7",
   "metadata": {},
   "source": [
    "### route_quad"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "pt = c << gf.components.pad_array(port_orientation=270, columns=3, centered_ports=False)\n",
    "pb = c << gf.components.pad_array(port_orientation=90, columns=3, centered_ports=False)\n",
    "pt.move((100, 200))\n",
    "\n",
    "# The route_quad function creates a U-shaped electrical trace.\n",
    "# It connects port e11 of the top pad array to port e11 of the bottom pad array on the specified metal layer (49, 0).\n",
    "gf.routing.route_quad(c, pt.ports[\"e11\"], pb.ports[\"e11\"], layer=(49, 0))\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9",
   "metadata": {},
   "source": [
    "### route_single"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "pt = c << gf.components.pad_array(port_orientation=270, columns=3, centered_ports=True)\n",
    "pb = c << gf.components.pad_array(port_orientation=90, columns=3, centered_ports=True)\n",
    "pt.move((100, 200))\n",
    "route = gf.routing.route_single( # The route_single function is an auto-router that creates a single waveguide or electrical trace.\n",
    "    c,\n",
    "    pb.ports[\"e11\"],\n",
    "    pt.ports[\"e11\"],\n",
    "    steps=[\n",
    "        {\"y\": 200},\n",
    "    ],\n",
    "    cross_section=\"metal_routing\",\n",
    "    bend=gf.components.wire_corner,\n",
    "    port_type=\"electrical\",\n",
    "    allow_width_mismatch=True,\n",
    "    auto_taper=False,\n",
    ")\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11",
   "metadata": {},
   "source": [
    "### route_bundle_electrical\n",
    "\n",
    "For routing groups of ports you can use `route_bundle`, which returns a bundle of routes using a bundle router (also known as bus or river router)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "pt = c << gf.components.pad_array(port_orientation=270, columns=3, centered_ports=False)\n",
    "pb = c << gf.components.pad_array(port_orientation=90, columns=3, centered_ports=False)\n",
    "pt.move((100, 300))\n",
    "\n",
    "routes = gf.routing.route_bundle_electrical(\n",
    "    c,\n",
    "    pb.ports,\n",
    "    pt.ports,\n",
    "    start_straight_length=30,\n",
    "    separation=30,\n",
    "    cross_section=\"metal_routing\",\n",
    ")\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13",
   "metadata": {},
   "source": [
    "## Routing to pads\n",
    "\n",
    "You can also route to electrical pads."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "14",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.components.straight_heater_metal(length=100.0)\n",
    "\n",
    "# The fanout_length parameter controls the length of the transitional section between waveguides.\n",
    "# A longer fanout results in more gradual, lower-loss bends.\n",
    "cc = gf.routing.add_pads_bot(component=c, port_names=(\"l_e4\", \"r_e4\"), fanout_length=80)\n",
    "cc.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "15",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "outputs": [],
   "source": [
    "c = gf.components.straight_heater_metal(length=100.0)\n",
    "cc = gf.routing.add_pads_bot(component=c, port_names=(\"l_e4\", \"r_e4\"), fanout_length=80)\n",
    "cc.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.components.straight_heater_metal(length=110)\n",
    "cc = gf.routing.add_pads_top(component=c, port_names=(\"l_e4\", \"r_e4\"), fanout_length=80)\n",
    "cc.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.c.nxn(\n",
    "    xsize=600,\n",
    "    ysize=200,\n",
    "    north=0,\n",
    "    south=3,\n",
    "    wg_width=10,\n",
    "    layer=\"M3\",\n",
    "    port_type=\"electrical\",\n",
    ")\n",
    "cc = gf.routing.add_pads_top(component=c, fanout_length=100)\n",
    "cc.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18",
   "metadata": {},
   "outputs": [],
   "source": [
    "n = west = north = south = east = 10\n",
    "spacing = 20\n",
    "c = gf.components.nxn(\n",
    "    xsize=n * spacing,\n",
    "    ysize=n * spacing,\n",
    "    west=west,\n",
    "    east=east,\n",
    "    north=north,\n",
    "    south=south,\n",
    "    port_type=\"electrical\",\n",
    "    wg_width=10,\n",
    "    layer=\"M3\",\n",
    ")\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "19",
   "metadata": {},
   "outputs": [],
   "source": [
    "cc = gf.routing.add_pads_top(component=c, fanout_length=-280)\n",
    "cc.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20",
   "metadata": {},
   "source": [
    "## Routing to optical terminations\n",
    "\n",
    "### Route to Fiber Array\n",
    "\n",
    "You can route to a fiber array."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21",
   "metadata": {},
   "outputs": [],
   "source": [
    "component = big_device(nports=10)\n",
    "c = gf.routing.add_fiber_array(component=component, radius=10.0, fanout_length=60.0)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22",
   "metadata": {},
   "source": [
    "You can also mix and match TE and TM grating couplers. Notice that the `TM` polarization grating coupler is bigger."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "\n",
    "c = gf.components.mzi_phase_shifter()\n",
    "gcte = gf.components.grating_coupler_te\n",
    "\n",
    "cc = gf.routing.add_fiber_array(\n",
    "    component=c,\n",
    "    grating_coupler=gf.components.grating_coupler_te,\n",
    "    radius=20,\n",
    ")\n",
    "cc.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "24",
   "metadata": {},
   "source": [
    "### Route to edge couplers\n",
    "\n",
    "You can also route edge couplers to a fiber array or to both sides of the chip.\n",
    "\n",
    "For routing to both sides you can follow different strategies:\n",
    "\n",
    "1. Place the edge couplers and route your components to the edge couplers.\n",
    "2. Extend your component ports to each side.\n",
    "3. Anything you imagine ...\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25",
   "metadata": {},
   "outputs": [],
   "source": [
    "from functools import partial\n",
    "\n",
    "import gdsfactory as gf\n",
    "import gdsfactory.components as pc\n",
    "from gdsfactory.generic_tech import LAYER\n",
    "\n",
    "\n",
    "@gf.cell\n",
    "def sample_reticle(\n",
    "    size=(1500, 2000),\n",
    "    ec=\"edge_coupler_silicon\",\n",
    "    bend_s=partial(gf.c.bend_s, size=(100, 100)),\n",
    ") -> gf.Component:\n",
    "    \"\"\"Returns MZI with edge couplers.\n",
    "\n",
    "    Args:\n",
    "        size: size of the reticle.\n",
    "        ec: edge coupler component name.\n",
    "        bend_s: bend_s component.\n",
    "    \"\"\"\n",
    "    mzis = [pc.mzi(length_x=lengths) for lengths in [100, 200, 300]]\n",
    "    copies = 3  # Number of copies of each component.\n",
    "    components = mzis * copies\n",
    "\n",
    "    xsizes = [component.xsize for component in components]\n",
    "    xsize_max = max(xsizes)\n",
    "    ec = gf.get_component(ec)\n",
    "    taper = pc.taper(width2=0.5)\n",
    "    components_ec = []\n",
    "\n",
    "    # xsize_max: The width of the main component itself.\n",
    "    # + 2 * taper.xsize: Adds the width of the two tapers attached to each side of the main component.\n",
    "    # + 2 * ec.xsize: Adds the width of the two edge couplers (ec) attached to the ends of the tapers.\n",
    "    # > size[0]: Compares this total calculated width to the available width of the reticle (size[0]).\n",
    "    # raise ValueError(...): If the component is too wide, this command halts the script and prints an error message explaining the problem.\n",
    "    if xsize_max + 2 * taper.xsize + 2 * ec.xsize > size[0]:\n",
    "        raise ValueError(\n",
    "            f\"Component xsize_max={xsize_max} is larger than reticle size[0]={size[0]}\"\n",
    "        )\n",
    "\n",
    "    if bend_s:\n",
    "        bend_s = gf.get_component(bend_s)\n",
    "\n",
    "    for component in components:\n",
    "        if bend_s:\n",
    "            component = gf.components.extend_ports(\n",
    "                component, extension=bend_s, port1=\"o1\", port2=\"o2\"\n",
    "            )\n",
    "            extension_length = (\n",
    "                size[0]\n",
    "                - 2 * taper.xsize # Subtracts the width of the two tapers.\n",
    "                - 2 * ec.xsize # Subtracts the width of the two edge couplers (ec).\n",
    "                - component.xsize # Subtracts the width of the main, central component.\n",
    "                - 2 * bend_s.xsize # Subtracts the width of the two S-bends (bend_s).\n",
    "            ) / 2 # The remaining distance is divided by two, because there will be one straight extension on each side of the component.\n",
    "        else:\n",
    "            extension_length = ( # is the precise length the straight extensions must have to make the structure fit perfectly within the target width size[0].\n",
    "                size[0] - 2 * taper.xsize - 2 * ec.xsize - component.xsize\n",
    "            ) / 2\n",
    "\n",
    "        component_extended = gf.components.extend_ports(\n",
    "            component,\n",
    "            extension=pc.straight(extension_length),\n",
    "            port2=\"o2\",\n",
    "            port1=\"o1\",\n",
    "        )\n",
    "\n",
    "        component_tapered = gf.components.extend_ports(\n",
    "            component_extended, extension=taper, port2=\"o2\", port1=\"o1\"\n",
    "        )\n",
    "        component_ec = gf.components.extend_ports(\n",
    "            component_tapered, extension=ec, port1=\"o1\", port2=\"o2\"\n",
    "        )\n",
    "        components_ec.append(component_ec)\n",
    "\n",
    "    c = gf.Component()\n",
    "    fp = c << pc.rectangle(size=size, layer=LAYER.FLOORPLAN)\n",
    "\n",
    "    text_offset_y = 10\n",
    "    text_offset_x = 100\n",
    "\n",
    "    grid = c << gf.grid_with_text(\n",
    "        components_ec,\n",
    "        shape=(len(components), 1),\n",
    "        text=partial(gf.c.text_rectangular, layer=LAYER.M3),\n",
    "        text_offsets=(\n",
    "            (-size[0] / 2 + text_offset_x, text_offset_y),\n",
    "            (+size[0] / 2 - text_offset_x - 160, text_offset_y),\n",
    "        ),\n",
    "    )\n",
    "    fp.x = grid.x\n",
    "    return c\n",
    "\n",
    "\n",
    "c = sample_reticle(bend_s=None)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "26",
   "metadata": {},
   "source": [
    "To avoid straight light you can also include an S-bend."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "27",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = sample_reticle()\n",
    "c.plot()"
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "custom_cell_magics": "kql"
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
